using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;

namespace OsdmSoftware.CommonLibrary
{
    public interface IPreviewControl
    {
        void DockToDialog(Panel panel, CompleteFileDialog dialog);
        void SelectedFileChanged();
        bool Visible { get; set; }
        void UndockFromDialog();
    }

    public partial class CompleteFileDialog: Form
    {
        public event EventHandler FileSelectionChanged;

        bool saveAsDialog;
        string selectedFileName;

        string currentPath = "\\My Documents";
        bool overwritePrompt = true;
        bool folderChooseMode = false;
        IPreviewControl previewControl;

        bool initing;
        string oldFileName = "";
        bool ignoreDoubleClick = false;
        bool editorModified = false;

        public string CurrentPath
        {
            get { return currentPath; }
            set
            {
                if (Directory.Exists(value))
                {
                    currentPath = value;
                    filePathLabel.Invalidate();
                }
            }
        }

        public bool SaveAsDialog
        {
            get
            {
                return saveAsDialog;
            }
            set
            {
                saveAsDialog = value;
                if (saveAsDialog)
                    filterLabel.Text = "Save as type";
                else
                    filterLabel.Text = "Files of type";
            }
        }

        public int FilterIndex
        {
            get
            {
                return filterComboBox.SelectedIndex + 1;
            }
            set
            {
                filterComboBox.SelectedIndex = value - 1;
            }
        }

        public bool FolderChooseMode
        {
            get
            {
                return folderChooseMode;
            }
            set
            {
                folderChooseMode = value;
                filterLabel.Visible = !folderChooseMode;
                filterComboBox.Visible = !folderChooseMode;
            }
        }

        public IPreviewControl PreviewControl
        {
            get
            {
                return previewControl;
            }
            set
            {
                if (previewControl != null)
                    previewControl.UndockFromDialog();

                previewControl = value;
                if (previewControl == null)
                {
                    previewCheckBox.Visible = false;
                    previewAndOptionsPanel.Visible = false;
                }
                else
                {
                    previewAndOptionsPanel.Visible = true;
                    previewControl.DockToDialog(previewAndOptionsPanel, this);
                    previewCheckBox.Visible = true;
                    previewControl.Visible = true;
                    previewCheckBox.Checked = true;
                }
            }
        }

        public string FileName
        {
            get
            {
                return selectedFileName;
            }
            set
            {
                selectedFileName = value;
                CurrentPath = Path.GetDirectoryName(selectedFileName);
                editorModified = true;
                initing = true;
                fileNameTextBox.Text = Path.GetFileName(selectedFileName);
                initing = false;
            }
        }

        public bool OverwritePrompt { get { return overwritePrompt; } set { overwritePrompt = value; } }
        public string Filter { get { return GetFilter(); } set { SetFilter(value); } }
        public string InitialDirectory { get { return currentPath; } set { CurrentPath = value; } }

        struct FilterItem
        {
            public string Description;
            public string[] FilterMasks;

            public FilterItem(string description, string[] filterMasks)
            {
                this.Description = description;
                this.FilterMasks = filterMasks;
            }

            public override string ToString()
            {
                return Description;
            }
        }

        public class FileItem
        {
            public string FileName;
            public long Size;
            public int ImageIndex; // 0 for directory, 1 for file

            public FileItem(string fileName, long size, int imageIndex)
            {
                this.FileName = fileName;
                this.Size = size;
                this.ImageIndex = imageIndex;
            }
        }

        public CompleteFileDialog()
        {
            InitializeComponent();
            Graphics gr = this.CreateGraphics();
            int size = Screen.PrimaryScreen.Bounds.Width;
            int dpi = (int) gr.DpiX;
            gr.Dispose();

            if (size * 6 / dpi < 20)
                previewAndOptionsPanel.Visible = false;

            DirectoryInfo di = new DirectoryInfo(InitialDirectory);
            if (!di.Exists)
                InitialDirectory = new string(Path.DirectorySeparatorChar, 1);
        }

        public void SetFilter(string filter)
        {
            string[] filterItems = filter.Split(new Char[] { '|' });
            Trace.Assert(filterItems.Length % 2 == 0,
                "Filter must contains even number of elements " +
                "(description|mask|description1|mask1...)");
            filterComboBox.Items.Clear();
            for (int i = 0; i < filterItems.Length; i += 2)
            {
                //string extension = filterItems[i + 1].Replace("*.", "");
                string[] masks = filterItems[i + 1].Split(new Char[] { ';' });
                Trace.Assert(masks.Length > 0, "You need to pass non-empty filter pattern, to select all files pass *.*");
                /*if (masks.Length > 1 && saveAsDialog)
                    Trace.Assert(false, "There should be only one filter pattern for SaveAsDialog");*/
                filterComboBox.Items.Add(new FilterItem(filterItems[i], masks));
            }

            filterComboBox.SelectedIndex = 0;
        }

        public string GetFilter()
        {
            string s = "";
            foreach (FilterItem item in filterComboBox.Items)
            {
                string s1 = "";
                foreach (string ext in item.FilterMasks)
                {
                    if (s1 != "")
                        s1 += ";";

                    s1 += ext;
                }

                if (s != "")
                    s += "|";
                s += item.Description + "|" + s1;
            }

            return s;
        }

        protected void OnFileSelectionChanged()
        {
            if (previewControl != null && previewControl.Visible && previewAndOptionsPanel.Visible)
                previewControl.SelectedFileChanged();
            if (FileSelectionChanged != null)
                FileSelectionChanged(this, EventArgs.Empty);
        }

        static int CompareDirectories(DirectoryInfo di1, DirectoryInfo di2)
        {
            return String.Compare(di1.Name, di2.Name);
        }

        static int CompareFiles(FileInfo di1, FileInfo di2)
        {
            return String.Compare(di1.Name, di2.Name);
        }

        static List<FileInfo> GetFilesInDirectory(DirectoryInfo di, string[] extensions)
        {
            List<FileInfo> result = new List<FileInfo>();
            foreach (string ext in extensions)
            {
                FileInfo[] files = di.GetFiles(ext);
                result.AddRange(files);
            }

            result.Sort(CompareFiles);

            return result;
        }

        void ReadContent()
        {
            ReadContent(null);
        }

        void ReadContent(string childDirectory)
        {
            ReadDirectoryContent(CurrentPath, filesListView, true, FolderChooseMode, childDirectory,
                filterComboBox.SelectedItem == null ? null :
                ((FilterItem) filterComboBox.SelectedItem).FilterMasks);

            if ((!editorModified) || (!saveAsDialog))
            {
                initing = true;
                fileNameTextBox.Text = "";
                editorModified = false;
                initing = false;
            }

            if (filesListView.SelectedIndex == -1)
            {
                selectedFileName = "";
                OnFileSelectionChanged();
            }
        }

        public static void ReadDirectoryContent(
            string directory,
            OwnerDrawnListBox filesListView,
            bool addUp,
            bool subdirectoriessOnly,
            string childDirectory,
            string[] filterMasks)
        {
            filesListView.Items.Clear();
            filesListView.SelectedIndex = -1;
            if (addUp)
            {
                FileItem up = new FileItem("..", 0, 2);
                filesListView.Items.Add(up);
            }

            DirectoryInfo di = new DirectoryInfo(directory);
            DirectoryInfo[] folders = di.GetDirectories();
            Array.Sort(folders, CompareDirectories);
            foreach (DirectoryInfo folder in folders)
            {
                FileItem item = new FileItem(folder.Name, 0, 0);
                filesListView.Items.Add(item);
                if (item.FileName == childDirectory)
                    filesListView.SelectedItem = item;
            }

            if (!subdirectoriessOnly)
            {
                List<FileInfo> files = GetFilesInDirectory(di, filterMasks);
                foreach (FileInfo file in files)
                {
                    FileItem item = new FileItem(file.Name, file.Length, 1);
                    filesListView.Items.Add(item);
                }
            }

            filesListView.UpdateItems();
            filesListView.EnsureVisible(
                filesListView.SelectedIndex == -1 ? 0 :
                filesListView.SelectedIndex);
            filesListView.Refresh();
        }

        public new DialogResult ShowDialog()
        {
            ReadContent();
            return base.ShowDialog();
        }

        private void filesListView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (initing)
                return;

            if (filesListView.SelectedItem == null)
            {
                selectedFileName = null;
                OnFileSelectionChanged();
                return;
            }

            if (FolderChooseMode || ((FileItem) filesListView.SelectedItem).ImageIndex == 1)
            {
                initing = true;
                fileNameTextBox.Text = ((FileItem) filesListView.SelectedItem).FileName;
                editorModified = false;
                initing = false;
                selectedFileName = Path.Combine(CurrentPath,
                    ((FileItem) filesListView.SelectedItem).FileName);
                OnFileSelectionChanged();
            }
            else
            {
                selectedFileName = null;
                OnFileSelectionChanged();
            }
        }

        void toParentDirectory()
        {
            DirectoryInfo di = new DirectoryInfo(CurrentPath);
            if (di.Parent != null)
                CurrentPath = di.Parent.FullName;

            ReadContent(di.Name);
        }

        private void upButton_Click(object sender, EventArgs e)
        {
            toParentDirectory();
        }

        private void filterComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            ReadContent();
        }

        private void fileNameTextBox_TextChanged(object sender, EventArgs e)
        {
            if (initing)
                return;

            editorModified = true;

            string original = fileNameTextBox.Text;
            if (fileNameTextBox.Text.IndexOf(Path.DirectorySeparatorChar) == -1)
            {
                FileItem foundItem = null;
                foreach (FileItem item in filesListView.Items)
                {
                    if (item.FileName.StartsWith(fileNameTextBox.Text,
                        StringComparison.CurrentCultureIgnoreCase))
                    {
                        foundItem = item;
                    }
                }

                if (foundItem != null)
                {
                    initing = true;
                    filesListView.SelectedItem = foundItem;
                    selectedFileName = null;
                    OnFileSelectionChanged();

                    filesListView.EnsureVisible(filesListView.SelectedIndex);
                    filesListView.Refresh();
                    //foundItem.Selected = true;
                    if (fileNameTextBox.SelectionStart == fileNameTextBox.Text.Length &&
                        fileNameTextBox.Text.StartsWith(oldFileName) &&
                        fileNameTextBox.Text != oldFileName)
                    {
                        int i = fileNameTextBox.SelectionStart;
                        string s = foundItem.FileName.Substring(i);
                        fileNameTextBox.Text += s;
                        fileNameTextBox.SelectionStart = i;
                        fileNameTextBox.SelectionLength = s.Length;
                    }

                    initing = false;
                }
            }
            else
            {
                if (fileNameTextBox.SelectionStart == fileNameTextBox.Text.Length &&
                    fileNameTextBox.Text.StartsWith(oldFileName) &&
                    fileNameTextBox.Text != oldFileName)
                {
                    string fullPath = Path.Combine(CurrentPath, fileNameTextBox.Text);
                    string dir = Path.GetDirectoryName(fullPath);
                    string name = Path.GetFileName(fullPath);
                    DirectoryInfo di = null;
                    try
                    {
                        di = new DirectoryInfo(dir);
                    }
                    catch
                    {
                        di = null;
                    }

                    if (di != null && di.Exists)
                    {
                        bool foundFile = false;
                        if (!FolderChooseMode)
                        {
                            List<FileInfo> files = GetFilesInDirectory(di,
                                ((FilterItem) filterComboBox.SelectedItem).FilterMasks);

                            /*FileInfo[] files = di.GetFiles(name + "*." + 
                                ((FilterItem) filterComboBox.SelectedItem).Extension);*/
                            if (files.Count > 0)
                            {
                                //Array.Sort(files, CompareFiles);
                                int i = fileNameTextBox.SelectionStart;
                                string s = files[0].Name.Substring(name.Length);
                                fileNameTextBox.Text += s;
                                fileNameTextBox.SelectionStart = i;
                                fileNameTextBox.SelectionLength = s.Length;
                                foundFile = true;
                            }
                        }

                        if (!foundFile)
                        {
                            DirectoryInfo[] directories = di.GetDirectories(name + "*");
                            if (directories.Length > 0)
                            {
                                Array.Sort(directories, CompareDirectories);
                                int i = fileNameTextBox.SelectionStart;
                                string s = directories[0].Name.Substring(name.Length);
                                fileNameTextBox.Text += s;
                                fileNameTextBox.SelectionStart = i;
                                fileNameTextBox.SelectionLength = s.Length;
                            }
                        }
                    }
                }

            }

            oldFileName = original;
        }

        private void fileNameTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char) Keys.Enter)
            {
                okMenuItem_Click(sender, EventArgs.Empty);

                e.Handled = true;
            }
        }

        private void ReturnResult(string result, bool newFile)
        {
            selectedFileName = result;

            if (saveAsDialog && newFile && folderChooseMode)
            {
                if (MessageBox.Show("Folder " + result + " does not exists. Create?", "Confirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) != DialogResult.OK)
                    return;

                Directory.CreateDirectory(result);
            }
            else if (saveAsDialog && newFile)
            {
                bool hasExtension = false;

                foreach (string filterMask in ((FilterItem) filterComboBox.SelectedItem).FilterMasks)
                {
                    string extension = filterMask.Replace("*.", "");
                    if (extension == "*")
                    {
                        hasExtension = true;
                        break;
                    }

                    if (selectedFileName.EndsWith("." + extension, StringComparison.CurrentCultureIgnoreCase))
                    {
                        hasExtension = true;
                        break;
                    }                        
                }

                if (!hasExtension)
                {
                    string extension = ((FilterItem) filterComboBox.SelectedItem).
                        FilterMasks[0].Replace("*.", "");
                    selectedFileName += "." + extension;
                }
            }
            else if (saveAsDialog && overwritePrompt)
            {
                string s = "";
                if (FolderChooseMode)
                    s = "folder";
                else
                    s = "file";

                if (MessageBox.Show("Overwrite " + s + " " + result + "?", "Confirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) != DialogResult.OK)
                    return;
            }

            //MessageBox.Show(selectedFileName);
            DialogResult = DialogResult.OK;
        }

        private void okMenuItem_Click(object sender, EventArgs e)
        {
            if (fileNameTextBox.Text.IndexOf(Path.DirectorySeparatorChar) == -1)
            {
                FileItem foundItem = null;
                foreach (FileItem item in filesListView.Items)
                {
                    if (item.FileName.Equals(fileNameTextBox.Text,
                        StringComparison.CurrentCultureIgnoreCase))
                    {
                        foundItem = item;
                    }
                }

                if (foundItem != null)
                {
                    if (foundItem.ImageIndex == 0 && !FolderChooseMode)
                    {
                        //foundItem.Selected = true;
                        CurrentPath = Path.Combine(CurrentPath, foundItem.FileName);
                        ReadContent();
                    }
                    else if (foundItem.ImageIndex == 2)
                    {
                        toParentDirectory();
                    }
                    else
                    {
                        ReturnResult(Path.Combine(CurrentPath, fileNameTextBox.Text), false);
                    }
                }
                else if (saveAsDialog)
                {
                    ReturnResult(Path.Combine(CurrentPath, fileNameTextBox.Text), true);
                }
            }
            else
            {
                string s1 = Path.Combine(CurrentPath, fileNameTextBox.Text);

                bool fileFound = false;
                if (!FolderChooseMode)
                {
                    FileInfo fi = new FileInfo(s1);
                    if (fi.Exists)
                    {
                        ReturnResult(Path.GetFullPath(fi.FullName), false);
                        fileFound = true;
                    }
                }

                if (! fileFound)
                {
                    DirectoryInfo di = new DirectoryInfo(s1);
                    if (di.Exists)
                    {
                        if (folderChooseMode)
                        {
                            ReturnResult(Path.GetFullPath(di.FullName), false);
                        }
                        else
                        {
                            CurrentPath = Path.GetFullPath(di.FullName);
                            ReadContent();
                        }
                    }
                    else if (saveAsDialog)
                        ReturnResult(s1, true);
                }
            }
        }

        private void filesListView_DrawItem(Graphics gs, object obj, int index, int x, int y,
            bool selected)
        {
            FileItem fi = (FileItem) obj;
            Image im = null;
            if (fi.ImageIndex == 0)
                im = ImageList.Images[fi.ImageIndex];
            filesListView.DrawBitmapAndText(gs, im, fi.FileName, x, y, selected, 1, Color.White,
                filesListView.ForeColor, sizeHeader.Left - filesListView.Left - 1);
            if (fi.ImageIndex == 1)
            {
                string s = "";
                if (fi.Size < 10000L)
                    s = fi.Size.ToString();
                else if (fi.Size < 10000000L)
                    s = (fi.Size / 1024L).ToString() + "k";
                else 
                    s = (fi.Size / (1024L * 1024L)).ToString() + "M";
                filesListView.DrawBitmapAndText(gs, null, s,
                    sizeHeader.Left - filesListView.Left,
                    y, selected, 1, Color.White);
            }
        }

        protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
        {
            ImageList.ImageSize = new Size(
                (int) (ImageList.ImageSize.Width * factor.Width),
                (int) (ImageList.ImageSize.Height * factor.Height));
            filesListView.ItemHeight = (int) (filesListView.ItemHeight * factor.Height);

            base.ScaleControl(factor, specified);
        }

        private void filePathLabel_Paint(object sender, PaintEventArgs e)
        {
            string s = Utility.CutDownStringToFitWidth(CurrentPath, e.Graphics, label1.Font, filePathLabel.ClientSize.Width);
            e.Graphics.DrawString(s, this.Font, new SolidBrush(label1.ForeColor), 1, 1);
        }

        private void filesListView_Click(object sender, EventArgs e)
        {
            ignoreDoubleClick = false;
            if (filesListView.SelectedItem == null)
                return;

            if (initing)
                return;

            if (((FileItem) filesListView.SelectedItem).ImageIndex == 0 && !folderChooseMode)
            {
                // Directory
                CurrentPath = Path.Combine(CurrentPath,
                    ((FileItem) filesListView.SelectedItem).FileName);
                ReadContent();
                ignoreDoubleClick = true;
            }
            else if (((FileItem) filesListView.SelectedItem).ImageIndex == 2)
            {
                // Parent directory
                toParentDirectory();
                ignoreDoubleClick = true;
            }
            else
            {
                if (selectedFileName == null)
                {
                    selectedFileName = Path.Combine(CurrentPath,
                       ((FileItem) filesListView.SelectedItem).FileName);
                    OnFileSelectionChanged();
                }
            }
        }

        private void filesListView_DoubleClick(object sender, EventArgs e)
        {
            if (ignoreDoubleClick)
                ignoreDoubleClick = false;
            else
            {
                if (((FileItem) filesListView.SelectedItem).ImageIndex == 0 && folderChooseMode)
                {
                    // Directory
                    CurrentPath = Path.Combine(CurrentPath,
                        ((FileItem) filesListView.SelectedItem).FileName);
                    ReadContent();
                    ignoreDoubleClick = true;
                }
                else
                    okMenuItem_Click(sender, e);
            }
        }

        private void previewCheckBox_CheckStateChanged(object sender, EventArgs e)
        {
            if (previewControl != null)
            {
                previewControl.Visible = previewCheckBox.Checked;
            }
        }

        private void menuItem2_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.Cancel;
        }
    }
}